odhcpd: support IAIDs for static DHCPv6 leases
authorDavid Härdeman <[email protected]>
Sat, 20 Sep 2025 16:19:06 +0000 (18:19 +0200)
committerÁlvaro Fernández Rojas <[email protected]>
Thu, 9 Oct 2025 06:58:23 +0000 (08:58 +0200)
Extend the string format for duids to alternatively support
"<duid>%<iaid>", which makes it possible to define separate static
leases, e.g. for a client which is connected to the same network with
more than one interface (which will both have the same DUID, as it is a
per-host identifier, but different IAIDs).

Note that this only wires up the new format, actually using it to make
DHCPv6-IA decisions will be implemented in subsequent patches.

Signed-off-by: David Härdeman <[email protected]>
Link: https://github.com/openwrt/odhcpd/pull/255
Signed-off-by: Álvaro Fernández Rojas <[email protected]>
README.md
src/config.c
src/odhcpd.h

index b710b93603392d799ffd4f6c3a6831874c105fc8..69d7a6c437d4a3575c13e6f7bed0da7498aaac41 100644 (file)
--- a/README.md
+++ b/README.md
@@ -125,7 +125,7 @@ and may also receive information from ubus
 | :-------------------- | :---- | :---- | :---------- |
 | ip                   |string |(none) | IPv4 host address |
 | mac                  |list\|string|(none) | HexadecimalMACaddress(es) |
-| duid                 |string |(none) | HexadecimalDUID |
+| duid                 |string |(none) | Hexadecimal DUID, or DUID%IAID |
 | hostid               |string |(none) | IPv6hostidentifier |
 | name                 |string |(none) | Hostname |
 | leasetime            |string |(none) | DHCPv4/v6leasetime |
index 0dcef1b568129f50d03184d37194fc24563e5dc7..5f072ead1f65802fd446872b16c2bd70a0d4010f 100644 (file)
@@ -469,7 +469,7 @@ int set_lease_from_blobmsg(struct blob_attr *ba)
        struct lease *l = NULL;
        int mac_count = 0;
        struct ether_addr *macs;
-       size_t duidlen = 0;
+       size_t duid_alloc_size = 0;
        uint8_t *duid;
 
        blobmsg_parse(lease_attrs, LEASE_ATTR_MAX, tb, blob_data(ba), blob_len(ba));
@@ -481,11 +481,12 @@ int set_lease_from_blobmsg(struct blob_attr *ba)
        }
 
        if ((c = tb[LEASE_ATTR_DUID]))
-               duidlen = (blobmsg_data_len(c) - 1) / 2;
+               /* We might overallocate some bytes here, that's fine */
+               duid_alloc_size = (blobmsg_data_len(c) - 1) / 2;
 
        l = calloc_a(sizeof(*l),
                     &macs, mac_count * sizeof(*macs),
-                    &duid, duidlen);
+                    &duid, duid_alloc_size);
        if (!l)
                goto err;
 
@@ -503,15 +504,50 @@ int set_lease_from_blobmsg(struct blob_attr *ba)
        }
 
        if ((c = tb[LEASE_ATTR_DUID])) {
-               ssize_t len;
+               const char *duid_str = blobmsg_get_string(c);
+               size_t duid_str_len = blobmsg_data_len(c) - 1;
+               ssize_t duid_len;
+               const char *iaid_str;
+
+               /* We support a hex string with either "<DUID>", or "<DUID>%<IAID>" */
+               iaid_str = strrchr(duid_str, '%');
+               if (iaid_str) {
+                       size_t iaid_str_len = strlen(++iaid_str);
+
+                       /* IAID = uint32, RFC8415, §21.4, §21.5, §21.21 */
+                       if (iaid_str_len < 1 || iaid_str_len > 2 * sizeof(uint32_t)) {
+                               syslog(LOG_ERR, "Invalid IAID length '%s'", iaid_str);
+                               goto err;
+                       }
 
-               l->duid = duid;
-               len = odhcpd_unhexlify(l->duid, duidlen, blobmsg_get_string(c));
+                       errno = 0;
+                       l->iaid = strtoull(iaid_str, NULL, 16);
+                       if (errno) {
+                               syslog(LOG_ERR, "Invalid IAID '%s'", iaid_str);
+                               goto err;
+                       }
+
+                       l->iaid_set = true;
+                       duid_str_len -= (iaid_str_len + 1);
+               }
+
+               if (duid_str_len < 2 || duid_str_len > DUID_MAX_LEN * 2 || duid_str_len % 2) {
+                       syslog(LOG_ERR, "Invalid DUID length '%.*s'", (int)duid_str_len, duid_str);
+                       goto err;
+               }
 
-               if (len < 0)
+               l->duid = duid;
+               duid_len = odhcpd_unhexlify(l->duid, duid_str_len / 2, duid_str);
+               if (duid_len < 0) {
+                       syslog(LOG_ERR, "Invalid DUID '%.*s'", (int)duid_str_len, duid_str);
                        goto err;
+               }
 
-               l->duid_len = len;
+               l->duid_len = duid_len;
+               syslog(LOG_DEBUG,
+                      "Found a static lease for DUID '%.*s' (%zi bytes), IAID 0x%08" PRIx32 "%s",
+                      (int)duid_str_len, duid_str, duid_len,
+                      l->iaid, l->iaid_set ? "" : " (not defined)");
        }
 
        if ((c = tb[LEASE_ATTR_NAME])) {
index 0903487d5dc43a2e89ead37cfbf03da317aee10e..e2251088459be2c6290635a2360de4595e467f25 100644 (file)
@@ -180,6 +180,8 @@ struct config {
        int log_level;
 };
 
+/* 2-byte type + 128-byte DUID, RFC8415, §11.1 */
+#define DUID_MAX_LEN 130
 
 struct lease {
        struct vlist_node node;
@@ -190,6 +192,8 @@ struct lease {
        struct ether_addr *macs;
        uint16_t duid_len;
        uint8_t *duid;
+       uint32_t iaid;
+       bool iaid_set;
        uint32_t leasetime;
        char *hostname;
 };